﻿using Inaction;
using Inaction.ServiceContract;
using Inaction.Core;
using Inaction.DbSchame;
using Inaction.Resource.Properties;
using Inaction.Rule;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

namespace Inaction.Mapping
{
    [Serializable]
    public class ClassInfo
    {
        #region 属性
        Type entityType;
        public Type EntityType
        {
            get { return entityType; }
        }
        private static Dictionary<Type, ClassInfo> classInfoCache = new Dictionary<Type, ClassInfo>();
        public List<IProperty> Properties { get; private set; }

        private IProperty primaryKey;
        public IProperty PrimaryKey
        {
            get
            {
                primaryKey = MappedProperties.Values.FirstOrDefault(p => p.Column.IsPrimaryKey);
                if (primaryKey == null)
                {
                    var temp = MappedProperties.FirstOrDefault(p => p.Value.Name.Equals("Id", StringComparison.OrdinalIgnoreCase));
                    try
                    {
                        primaryKey = temp.Value;
                    }
                    catch { }
                }
                return primaryKey;
            }
        }

        private IEnumerable<IProperty> defaultValueProperties = null;
        public IEnumerable<IProperty> DefaultValueProperties
        {
            get
            {
                if (defaultValueProperties == null)
                {
                    defaultValueProperties = MappedProperties.Values.Where(p => p.DefaultValue != null).ToList();
                }
                return defaultValueProperties;
            }
        }

        private Dictionary<string, List<IProperty>> uniqueProperties = null;
        public Dictionary<string, List<IProperty>> UniqueProperties
        {
            get
            {
                if (uniqueProperties == null)
                {
                    var uniqueKeys = Table.UniqueKeys.Where(p => p.Table.Name == TableName);
                    uniqueProperties = new Dictionary<string, List<IProperty>>();
                    foreach (var item in uniqueKeys)
                    {
                        if (!uniqueProperties.ContainsKey(item.Name))
                        {
                            uniqueProperties.Add(item.Name, new List<IProperty>());
                        }
                        var list = uniqueProperties[item.Name];
                        foreach (var column in item.Columns)
                        {
                            list.Add(ColumnProperties[column.Name]);
                        }
                    }
                }
                return uniqueProperties;
            }
        }

        private IEnumerable<IProperty> foreignKeyProperties = null;
        public IEnumerable<IProperty> ForeignKeyProperties
        {
            get
            {
                if (foreignKeyProperties == null)
                {
                    foreignKeyProperties = MappedProperties.Values.Where(p => p.Column.IsForeignKey == true).ToList();
                }
                return foreignKeyProperties;
            }
        }

        private Dictionary<string, IProperty> columnProperties = new Dictionary<string, IProperty>();
        public Dictionary<string, IProperty> ColumnProperties
        {
            get
            {
                if (columnProperties.Count == 0)
                {
                    lock (columnProperties)
                    {
                        columnProperties = new Dictionary<string, IProperty>();
                        foreach (var p in MappedProperties.Values)
                        {
                            if (p.Column.Table.Name == TableName)
                            {
                                columnProperties.Add(p.Column.Name, p);
                            }
                        }
                    }
                }
                return columnProperties;
            }
        }

        private Dictionary<string, IProperty> mappedProperties = new Dictionary<string, IProperty>();
        public Dictionary<string, IProperty> MappedProperties
        {
            get
            {
                if (mappedProperties.Count == 0)
                {
                    lock (mappedProperties)
                    {
                        mappedProperties = new Dictionary<string, IProperty>();
                        foreach (var p in Properties)
                        {
                            if (p.Column != null)
                            {
                                mappedProperties.Add(p.Column.Name, p);
                            }
                        }
                    }
                }
                return mappedProperties;
            }
        }

        private Dictionary<string, IProperty> nameProperties = new Dictionary<string, IProperty>();
        public Dictionary<string, IProperty> NameProperties
        {
            get
            {
                if (nameProperties.Count == 0)
                {
                    lock (nameProperties)
                    {
                        nameProperties = new Dictionary<string, IProperty>();
                        foreach (var p in Properties)
                        {
                            nameProperties.Add(p.Name, p);
                        }
                    }
                }
                return nameProperties;
            }
        }
        public Property<string> CreaterProperty { get; internal set; }
        public Property<DateTime?> CreateTimeProperty { get; internal set; }
        public Property<DateTime?> UpdateTimeProperty { get; internal set; }
        public Property<string> UpdaterProperty { get; internal set; }
        public Property<bool?> DeletedProperty { get; internal set; }

        #region Table信息

        public string FormatTableName
        {
            get
            {
                return Inaction.DbProvider.Instance.FormatName(TableName);
            }
        }
        public string TableName { get; private set; }
        public string DisplayName { get; private set; }
        public string SelectView { get; private set; }
        public string SelectSql { get; private set; }
        public Table Table { get; private set; }
        #endregion

        #endregion

        #region 构造
        private ClassInfo()
        {
            Properties = new List<IProperty>();
        }
        #endregion

        #region 方法
        public static ClassInfo LoadInfo(Type type)
        {
            if (classInfoCache.ContainsKey(type))
            {
                var classInfo = classInfoCache[type];
                return classInfo;
            }
            ClassInfo info = new ClassInfo() { entityType = type };
            var tableAttribute = type.GetCustomAttribute<TableAttribute>();
            if (tableAttribute != null)
            {
                info.TableName = tableAttribute.TableName;
                info.SelectSql = tableAttribute.SelectSql;
                info.SelectView = tableAttribute.SelectView;
                if (string.IsNullOrEmpty(info.SelectView))
                {
                    info.Table = DbProvider.Instance.GetTable(info.TableName);
                    if (info.Table == null)
                    {
                        info.Table = DbProvider.Instance.GetView(info.TableName);
                    }
                }
                else
                {
                    info.Table = DbProvider.Instance.GetView(info.SelectView);
                }
                if (info.Table == null) throw new Exception(string.Format(Resources.TableNotExists, info.TableName));
            }
            else
            {
                info.Table = DbProvider.Instance.GetTable(type.Name);
                if (info.Table == null) throw new Exception(string.Format(Resources.TableNotExists, type.Name));
                info.TableName = info.Table.Name;
            }
            info.DisplayName = info.Table.DisplayName;
            if (string.IsNullOrEmpty(info.DisplayName))
            {
                var displayName = type.GetCustomAttribute<DisplayNameAttribute>();
                if (displayName != null)
                {
                    info.DisplayName = displayName.DisplayName;
                }
                else if (!string.IsNullOrEmpty(info.Table.DisplayName))
                {
                    info.DisplayName = info.Table.DisplayName;
                }
                else
                {
                    info.DisplayName = type.FullName;
                }
            }
            classInfoCache.Add(type, info);
            while (true)
            {
                var fields = type.GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.GetField);
                foreach (var field in fields)
                {
                    if (field.FieldType.GetInterface(typeof(IProperty).Name) != null)
                    {
                        field.GetValue(null);
                        break;
                    }
                }
                type = type.BaseType;
                if (type == null || type.Equals(typeof(ModelBase))) break;
            }
            return info;
        }

        internal static void ClearCache()
        {
            classInfoCache.Clear();
        }

        internal static IProperty GetProperty(Type type, string property)
        {
            var classInfo = ClassInfo.LoadInfo(type);
            IProperty p = null;
            if (!classInfo.NameProperties.ContainsKey(property))
            {
                throw new Exception("classInfo.NameProperties");
            }
            p = classInfo.NameProperties[property];
            if (p.Column == null)
            {
                throw new Exception("classInfo.ColumnProperties");
            }
            return p;
        }

        #region AddtionRelationship
        List<RelationshipInfo> addtionRelationship = new List<RelationshipInfo>();
        internal void AddChildRelationship<Master>(Property<Guid?> childFKProperty)
        {
            if (addtionRelationship.Any(p => p.ChildFKProperty == childFKProperty)) return;
            var masterType = typeof(Master);
            var masterInfo = ClassInfo.LoadInfo(masterType);
            addtionRelationship.Add(
                new RelationshipInfo
                {
                    ChildType = EntityType,
                    MasterType = EntityType,
                    ChildFKProperty = childFKProperty
                });
        }

        internal void AddMasterRelationship<Child>(Property<Guid?> childFKProperty)
        {
            if (addtionRelationship.Any(p => p.ChildFKProperty == childFKProperty)) return;
            var childType = typeof(Child);
            var childInfo = ClassInfo.LoadInfo(childType);
            var masterInfo = this;
            addtionRelationship.Add(
                new RelationshipInfo
                {
                    ChildType = childType,
                    MasterType = EntityType,
                    ChildFKProperty = childFKProperty
                });
        }
        public Property<Guid?> GetChildAddtionRelationship(Type childType)
        {
            if (addtionRelationship.Count > 0)
            {
                var relationship = addtionRelationship.FirstOrDefault(p => p.ChildType == childType);
                return relationship == null ? null : relationship.ChildFKProperty;
            }
            return null;
        }
        public Property<Guid?> GetMasterAddtionRelationship(Type masterType)
        {
            if (addtionRelationship.Count > 0)
            {
                var relationship = addtionRelationship.FirstOrDefault(p => p.MasterType == masterType);
                return relationship == null ? null : relationship.ChildFKProperty;
            }
            return null;
        }

        #endregion

        #region RegisteForeignKeyDefaultValue
        static void RegisteForeignKeyDefaultValue(string masterTableName, Guid primaryKeyValue)
        {
            var table = Inaction.DbProvider.Instance.Tables.FirstOrDefault(p => p.Name.Equals(masterTableName, StringComparison.OrdinalIgnoreCase));
            foreach (var fkChild in table.ForeignKeyChildren)
            {
                foreach (var fk in fkChild.ForeignKeys)
                {
                    if (fk.ReferenceTable.Name.Equals(masterTableName))
                    {
                        fk.Column.DefaultValue = primaryKeyValue;
                    }
                }
            }
        }
        static void RegisteForeignKeyDefaultValue(Type masterType, Guid primaryKeyValue)
        {
            var tableName = LoadInfo(masterType).TableName;
            RegisteForeignKeyDefaultValue(tableName, primaryKeyValue);
        }
        #endregion

        #region RegisteForeignKeyDefaultCriteria
        static Dictionary<Table, Dictionary<Column, List<Guid>>> defaultCriteriaValues = new Dictionary<Table, Dictionary<Column, List<Guid>>>();
        public Dictionary<Column, List<Guid>> DefaultCriteriaValues
        {
            get
            {
                if (defaultCriteriaValues.ContainsKey(this.Table))
                    return defaultCriteriaValues[this.Table];
                return null;
            }
        }
        static void RegisteForeignKeyDefaultCriteria(string masterTableName, params Guid[] values)
        {
            var table = Inaction.DbProvider.Instance.Tables.FirstOrDefault(p => p.Name.Equals(masterTableName, StringComparison.OrdinalIgnoreCase));
            foreach (var fkChild in table.ForeignKeyChildren)
            {
                if (!defaultCriteriaValues.ContainsKey(fkChild))
                {
                    defaultCriteriaValues.Add(fkChild, new Dictionary<Column, List<Guid>>());
                }
                var tableConfig = defaultCriteriaValues[fkChild];
                foreach (var fk in fkChild.ForeignKeys)
                {
                    if (!fk.ReferenceTable.Name.Equals(masterTableName)) continue;
                    if (!tableConfig.ContainsKey(fk.Column))
                    {
                        tableConfig.Add(fk.Column, new List<Guid>());
                    }
                    var columnConfig = tableConfig[fk.Column];
                    foreach (var guid in values)
                    {
                        if (columnConfig.Contains(guid)) continue;
                        columnConfig.Add(guid);
                    }
                }
            }
        }
        static void RegisteForeignKeyDefaultCriteria(Type masterType, params Guid[] values)
        {
            var tableName = LoadInfo(masterType).TableName;
            RegisteForeignKeyDefaultCriteria(tableName, values);
        }
        #endregion

        #endregion
    }

    class RelationshipInfo
    {
        public Type ChildType { get; set; }
        public Type MasterType { get; set; }
        public Property<Guid?> ChildFKProperty { get; set; }
    }
}